package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory1;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import io.prometheus.client.Histogram;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;



@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private static final String INDEX_NAME = "albuminfo";

    @Autowired
    private static final String INDEX_NAME_SUGGEST = "suggestinfo";


    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;

    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;



    @Override
    public void upperAlbum(Long albumId) {
        // 1.构建索引库文档对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        // 2.封装专辑及专辑标签属性 - 远程调用专辑服务获取专辑信息（包含专辑标签列表）
        // 2.1 处理专辑基本信息
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(
                ()->{
                    AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
                    Assert.notNull(albumInfo,"专辑不存在，专辑ID{}",albumId);
                    BeanUtils.copyProperties(albumInfo,albumInfoIndex);

                    // 2.2 处理专辑标签列表
                    List<AlbumAttributeValue> albumAttributeValueList = albumInfo.getAlbumAttributeValueVoList();
                    if (CollectionUtils.isNotEmpty(albumAttributeValueList)){
                        List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueList.stream().map(albumAttributeValue -> {
                            // 将专辑标签集合泛型从 AlbumAttributeValue 转为 AttributeValueIndex
                            AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                            attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
                            attributeValueIndex.setValueId(albumAttributeValue.getValueId());
                            return attributeValueIndex;
                        }).collect(Collectors.toList());
                        albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
                    }
                    return albumInfo;
                },threadPoolExecutor
        );

        // 3. 封装分类信息 - 远程调用专辑服务获取分类视图对象，依赖专辑异步任务，当前任务不需要返回值
        CompletableFuture<Void> basecategoryViewCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(
                albumInfo -> {
                    BaseCategoryView baseCategoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
                    Assert.notNull(baseCategoryView,"分类不存在，分类ID：{}",albumInfo.getCategory3Id());
                    albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
                    albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());

                },threadPoolExecutor
        );

        // 4. 封装主播名称 - 远程调用用户服务获取用户信息
        CompletableFuture<Void> userInfoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(
                albumInfo -> {
                    UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData();
                    Assert.notNull(userInfoVo,"主播信息不存在，主播ID：{}",albumInfo.getUserId());
                    albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
                },threadPoolExecutor
        );

        // 5.TODO 封装统计信息系，采用产生随机值，以及专辑热度
        CompletableFuture<Void> statCompletableFuture = CompletableFuture.runAsync(
                ()->{
                    // 5.1 随机为专辑产生播放量，订阅量，购买量，评论量
                    int num1 = RandomUtil.randomInt(1000,2000);
                    int num2 = RandomUtil.randomInt(500,1000);
                    int num3 = RandomUtil.randomInt(200,400);
                    int num4 = RandomUtil.randomInt(100,200);

                    albumInfoIndex.setPlayStatNum(num1);
                    albumInfoIndex.setSubscribeStatNum(num2);
                    albumInfoIndex.setBuyStatNum(num3);
                    albumInfoIndex.setCommentStatNum(num4);

                    // 5.2 基于统计值计算出专辑得分，为不同的统计值类型设置不同的权重
                    BigDecimal bigDecimal1 = new BigDecimal(num4).multiply(new BigDecimal("0.4"));
                    BigDecimal bigDecimal2 = new BigDecimal(num3).multiply(new BigDecimal("0.3"));
                    BigDecimal bigDecimal3 = new BigDecimal(num2).multiply(new BigDecimal("0.2"));
                    BigDecimal bigDecimal4 = new BigDecimal(num1).multiply(new BigDecimal("0.1"));

                    BigDecimal hotScore = bigDecimal1.add(bigDecimal2).add(bigDecimal3).add(bigDecimal4);

                    albumInfoIndex.setHotScore(hotScore.doubleValue());
                },threadPoolExecutor
        );

        // 6.组合异步任务对象 - 需求将四个异步任务必须全部执行完毕，主线程继续
        CompletableFuture.allOf(
                statCompletableFuture,
                userInfoCompletableFuture,
                basecategoryViewCompletableFuture,
                albumInfoCompletableFuture
        ).join();

        // 7.将索引库文档对象存入索引库
        albumInfoIndexRepository.save(albumInfoIndex);


    }

    /**
     * 删除专辑索引库文档
     * @param albumId
     */
    @Override
    public void lowerAlbum(Long albumId) {
        albumInfoIndexRepository.deleteById(albumId);
    }


    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private ElasticsearchClient elasticsearchClient;

    /**
     * 获取不同分类下不同排序方式榜单专辑列表
     */
    @Override
    public void updateLatelyAlbumRanking() {
        // 1.远程调用专辑服务获取所有一级分类
        try {
            List<BaseCategory1> category1List = albumFeignClient.getAllCategory1().getData();
            Assert.notNull(category1List,"一级分类为空");

            // 2.循环遍历一级分类，获取该分类下5种不同的排序方式的榜单专辑
            for (BaseCategory1 baseCategory1 : category1List) {
                Long category1Id = baseCategory1.getId();
                // 3.处理当前一级分类中，再次循环5种不同的排序方式得到的具体榜单数据
                // 3.1 声明排序方式数组
                String[] rankingDimensionArray = new String[]{"hotScore","playStatNum","subscribeStatNum","buyStatNum","commentStatNum"};


                for (String rankDimension : rankingDimensionArray) {
                    // 3.2 调用 ES 检索接口获取榜单数据
                    SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                            s -> s.index(INDEX_NAME)
                                    .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                                    .sort(sort -> sort.field(f -> f.field(rankDimension).order(SortOrder.Desc)))
                                    .size(10),
                            AlbumInfoIndex.class
                    );

                    // 3.3 获取当前分类下某个排序方式的榜单专辑列表
                    // 获取搜索结果中的文档列表。然后调用hits（）方法，将文档列表转化为 List<Hit<AlbumInfoIndex>> 类型
                    List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
                    if (CollectionUtils.isNotEmpty(hits)){
                        List<AlbumInfoIndex> list = hits.stream()
                                .map(hit -> hit.source()/* .source 方法的作用是获取一个名为hit的对象的source属性值*/)
                                .collect(Collectors.toList());
                        // 4.将榜单专辑列表存入 Redis-hash 中
                        // 4.1 声明Redis排行榜Hash接口 Key 形式：前缀+1级分类ID     field：排序方式  val:榜单列表
                        // 定义redis key
                        String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;

                        //4.2 将当前分类榜单数据放入Redis中
                        redisTemplate.opsForHash().put(key,rankDimension,list);
                    }
                }
            }
        } catch (Exception e) {
            log.error("[搜索服务]更新排行榜异常：{}", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 获取排行榜单
     * @param category1Id
     * @param dimension
     * @return
     */
    @Override
    public List<AlbumInfoIndex> getRankingList(Long category1Id, String dimension) {

        String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;

        List<AlbumInfoIndex> list =(List) redisTemplate.opsForHash().get(key, dimension);

        return list;
    }
}
